home *** CD-ROM | disk | FTP | other *** search
Wrap
Text File | 1991-02-21 | 44.8 KB | 1,174 lines | [ TEXT/PJMM]
unit UProcessUtils; {-------------------------------------------------------------------------------} {#} {# Apple Macintosh Developer Technical Support} {#} {# Interfaces for the guts of the ProcDoggie application} {#} {# Program: ProcDoggie} {# File: UProcessUtils.p - Pascal Implementation} {#} {# by: Forrest Tanaka} {#} {# Copyright © 1988-1991 Apple Computer, Inc.} {# All rights reserved.} {#} {--------------------------------------------------------------------------------} {#} {# This unit is a high-level interface to the Process Manager. It contains} {# routines which allow you to launch a process, terminate a process, count the} {# number of open processes, find a process given a file specification, and} {# manage a list of documents for a process to open or print when it is} {# launched.} {#} {# The LaunchProcess routine is the most important routine in this unit. It} {# lets you launch either an application or a desk accessory and optionally lets} {# you pass it a list of documents for the launched application to open or print} {# (desk accessories don’t have document lists).} {#} {-------------------------------------------------------------------------------} {[j=20/57/1$] Pasmat Options} {-------------------------------------------------------------------------------} {#} {# 2/21/91 pvh - THINK Pascal conversion.} {# Notes:} {#} {#} {-------------------------------------------------------------------------------} interface (*******************************************************************************} {* Used Units} {*******************************************************************************) uses (* Group 1 *) Types, QuickDraw, (* Group 2 *) AppleTalk, PPCToolBox, OSUtils, Files, Processes, EPPC, Notification, AppleEvents, Errors, Events, Memory, Resources, SegLoad, (* Group 3 *) Aliases; (*******************************************************************************} {* Types} {*******************************************************************************) type (* Flag to indicate whether document list is for printing or opening *) LaunchModeCode = (kJustLaunch, kOpenLaunch, kPrintLaunch); (* Document list entry for opening apps with docs to open or print *) DocListEntryPtr = ^DocListEntryRec; DocListEntryHnd = ^DocListEntryPtr; DocListEntryRec = record next: DocListEntryHnd; {Link to the next document list record} docFile: FSSpec {File specification} end; (* Document list for opening apps with docs to open or print *) DocListRec = record docList: DocListEntryHnd; {Handle to the first document list entry} openPrint: LaunchModeCode {Opening or printing documents?} end; DocListPtr = ^DocListRec; DocListHnd = ^DocListPtr; (*******************************************************************************} {* CreateDocList - Create a new document list} {*} {* This routine creates a new document list and returns a handle to it. If there} {* isn’t enough memory for a new document list, CreateDocList returns NIL.} {*} {* If openOrPrint is kOpenLaunch, then the list of documents that are put into} {* the new document list will be opened when the application is launched. If} {* openOrPrint is kPrintLaunch, then the list of documents will be printed when} {* the application is launched. If kJustLaunch or any other value is passed in} {* openOrPrint, then no document list is created. CreateDocList returns NIL in} {* this case. This isn’t really a conflict with returning NIL when there’s not} {* enough memory for the new document list because the calling routine can check} {* to see if openOrPrint is valid or not itself.} {*******************************************************************************) function CreateDocList (openOrPrint: LaunchModeCode): DocListHnd; (*******************************************************************************} {* IsEmptyDocList - Return TRUE if the specified document list is empty.} {*} {* IsEmptyDocList returns TRUE if the document list specified by "theList" is} {* empty. “Empty” can mean either that theList is NIL or theList is a handle to} {* a document list that contains no documents. It returns FALSE if there’s at} {* least one entry in "theList".} {*} {* theList must either be a valid handle to a document list or it must be NIL.} {* IsEmptyDocList is unpredictable if this isn’t true.} {*******************************************************************************) function IsEmptyDocList (theList: DocListHnd): Boolean; (*******************************************************************************} {* AddToDocList - Add a document file specification to a document list} {*} {* AddToDocList adds the file specified by "newFile" to the end of the document} {* list specified by "theList". If there isn’t enough memory to add a new file} {* to the document list, then AddToDocList returns memFullErr. Otherwise, noErr} {* is returned.} {*} {* theList MUST be a valid handle to an initialized document list. I won’t be} {* responsible for AddToDocList’s actions if this isn’t true.} {*******************************************************************************) function AddToDocList (newFile: FSSpec; theList: DocListHnd): OSErr; (*******************************************************************************} {* DisposeDocList - Dispose of a document list} {*} {* DisposeDocList deallocates all the memory occupied by the document list} {* specified by the "theList" parameter. If theList is NIL, then nothing is} {* done. If it’s not a valid DocListHnd, DisposeDocList is unpredictable.} {*******************************************************************************) procedure DisposeDocList (theList: DocListHnd); (*******************************************************************************} {* WereInFront - Test to see if this application is in front or not} {*} {* This routine determines whether this application is the front-most application} {* or not. If it is, then TRUE is returned. If it isn’t, then FALSE is} {* returned.} {*******************************************************************************) function WereInFront: Boolean; (*******************************************************************************} {* FindProcess - Find a process with specified file and name} {*} {* FindProcess searches the Process Manager’s process list for a process that was} {* launched from the file specified by "testFile". If the process being searched} {* is a DA, then the "daName" parameter must specify the name of the desk} {* accessory (i.e. the name of the DRVR resource, including the initial null} {* character). If the process is found, then information about the process is} {* returned in the "testFileInfo" parameter, and FindProcess returns TRUE. If} {* the process could not be found, then FALSE is returned, and "testFileInfo" is} {* unchanged.} {*} {* Warning: the processName and processAppSpec fields of "testFileInfo" are NOT} {* valid when FindProcess returns TRUE. If the name and FSSpec of the found file} {* are desired, then the calling routine must allocate space for those fields} {* and call GetProcessInfo itself, passing the ProcessInfoRec returned by this} {* routine as a parameter.} {*******************************************************************************) function FindProcess (testFile: FSSpec; daName: StringPtr; var testFileInfo: ProcessInfoRec): Boolean; (*******************************************************************************} {* LaunchProcess - Launch the specified process} {*} {* This routine launches the process whose file has the location and name} {* specified by "processFile" parameter. The process’s resulting serial number} {* is returned in the returnPSN parameter. If the process to be launched is a} {* desk accessory, then the name of the desk accessory (including the initial} {* null character that desk accessories require) is passed in daName. IF NIL is} {* passed in daName, then the first desk accessory found in the specified file} {* (according to the Resource Manager) is launched. If an application is being} {* launched, then daName is ignored. The options parameter specifies options to} {* use when launching the new process. It has the same values and meanings as} {* the LaunchFlags type defined in the Process Manager chapter of Inside} {* Macintosh VI. If a desk accessory is being launched, then only the} {* launchContinue flag has significance.} {*} {* A list of documents to be opened or printed by the launched application can} {* optionally be specified by the docList parameter. If no documents are} {* specified, then docList should be NIL. See the document list routines defined} {* early in this unit.} {*} {* LaunchProcess determines whether to launch an application or desk accessory} {* based on the type code of the file specified by processFile. If the file’s} {* type is APPL, then LaunchProcess attempts to launch it as an application. If} {* the file has any other type, then LaunchProcess attempts to launch a desk} {* accessory in that file.} {*} {* LaunchProcess returns two kinds of errors. One error is returned as a} {* function result. These kinds of errors are generated by any call that occurs} {* during the execution of LaunchProcess that is only used to manage the} {* launching of the specified application rather than launching the application} {* itself. The launchError parameter returns the error code of any error that} {* occurs when the application is actually launched.} {*******************************************************************************) function LaunchProcess (processFile: FSSpec; daName: StringPtr; docList: DocListHnd; options: LaunchFlags; var returnPSN: ProcessSerialNumber; var launchError: OSErr): OSErr; (*******************************************************************************} {* CountProcesses - Count the number of open processes} {*} {* This routine searches through the Process Manager’s process list and counts} {* the number of open processes. The result is returned.} {*******************************************************************************) function CountProcesses: Integer; (*******************************************************************************} {* TerminateProcess - Terminate a process} {*} {* This routine causes the process specified by "theProcessNum" to be terminated.} {* If an error occurs, then the error code is returned.} {*******************************************************************************) function TerminateProcess (theProcessNum: ProcessSerialNumber): OSErr; implementation const kCaseSens = TRUE; {Pass to EqualString for case-sensitive check} kDiacSens = TRUE; {Pass to EqualString for diacritical-sensitive check} {$S ProcessUtils} (*******************************************************************************} {* Private: EqualFSSpec - Check equality of FSSpec records} {*} {* EqualFSSpec returns TRUE if the file specified by spec1 refers to the same} {* file as the one specified by spec2. Otherwise, EqualFSSpec returns FALSE.} {*} {* To compare names, I’m using EqualString with no case sensitivity, but with} {* sensitivity to diacriticals. This isn’t usually the recommended way of} {* comparing strings, because the Script Manager has routines for comparing} {* strings in a more sophisticated, localizable way. But the File Manager uses} {* _CmpString, which is the assembly language equivalent of EqualString, so} {* that’s the way I must do things here.} {*******************************************************************************) function EqualFSSpec (spec1: FSSpec; spec2: FSSpec): Boolean; begin EqualFSSpec := (spec1.vRefNum = spec2.vRefNum) and (spec1.parID = spec2.parID) and (EqualString(spec1.name, spec2.name, not kCaseSens, kDiacSens)) end; {$S ProcessUtils} (*******************************************************************************} {* Public: CreateDocList} {*} {* A document list record is allocated and initialized. Pretty simple.} {*******************************************************************************) function CreateDocList (openOrPrint: LaunchModeCode): DocListHnd; var newList: DocListHnd; {Handle to the new DocListRec} begin if (openOrPrint = kOpenLaunch) or (openOrPrint = kPrintLaunch) then begin newList := DocListHnd(NewHandle(SIZEOF(DocListRec))); if newList <> nil then begin newList^^.docList := nil; newList^^.openPrint := openOrPrint end; CreateDocList := newList end end; {$S ProcessUtils} (*******************************************************************************} {* Public: IsEmptyDocList} {*} {* If the document list record is NIL, or if the docList field is NIL, then the} {* given document list is empty.} {*******************************************************************************) function IsEmptyDocList (theList: DocListHnd): Boolean; begin IsEmptyDocList := (theList = nil) | (theList^^.docList = nil) end; {$S ProcessUtils} (*******************************************************************************} {* Public: AddToDocList} {*} {* A new document list entry is allocated and then "newFile" is copied into its} {* docFile field. It is then added to end of "theList".} {*******************************************************************************) function AddToDocList (newFile: FSSpec; theList: DocListHnd): OSErr; var newEntry: DocListEntryHnd; {Handle to the new DocListEntryRec} currEntry: DocListEntryHnd; {Handle to DocListEntryRec being checked} begin (* Create a new document list entry *) newEntry := DocListEntryHnd(NewHandle(SIZEOF(DocListEntryRec))); if newEntry <> nil then begin (* Initialize the new document list entry *) newEntry^^.next := nil; newEntry^^.docFile := newFile; if IsEmptyDocList(theList) then theList^^.docList := newEntry else begin (* Start from the head of the list *) currEntry := theList^^.docList; (* Find the end of the list *) while currEntry^^.next <> nil do currEntry := currEntry^^.next; (* Attach the new entry to the end of the list *) currEntry^^.next := newEntry end; AddToDocList := noErr end else AddToDocList := MemError end; {$S ProcessUtils} (*******************************************************************************} {* Private: DisposeAllDocEntries - Dispose of all document list entries} {*} {* Ahhh. . . good old, basic, text-book recursion. This routine has a very small} {* stack frame, so the recursion isn’t very expensive in this case. Document} {* lists are normally very short too.} {*} {* This routine disposes of every document list entry in a linked list of} {* document list entries starting from the entry docListEntry. If docListEntry} {* isn’t NIL and it isn’t a valid DocListEntryHnd, DisposeAllDocEntries probably} {* blows up in very bad ways.} {*******************************************************************************) procedure DisposeAllDocEntries (docListEntry: DocListEntryHnd); begin if docListEntry <> nil then begin if docListEntry^^.next <> nil then DisposeAllDocEntries(docListEntry^^.next); DisposHandle(Handle(docListEntry)) end end; {$S ProcessUtils} (*******************************************************************************} {* Public: DisposeDocList} {*} {* Should be pretty obvious how this works.} {*******************************************************************************) procedure DisposeDocList (theList: DocListHnd); begin if theList <> nil then begin (* Dispose of all document list entries *) DisposeAllDocEntries(theList^^.docList); (* Dispose of the document list record *) DisposHandle(Handle(theList)) end end; {$S ProcessUtils} (*******************************************************************************} {* Public: WereInFront} {*} {* The current application’s process serial number can always be represented by} {* setting the high long integer to 0 and setting the low long integer to the} {* constant kCurrentProcess. This process serial number is compared against the} {* process serial number of the front application. If they’re the same, then} {* this application is the front application.} {*******************************************************************************) function WereInFront: Boolean; var thisProcess: ProcessSerialNumber; {Current process’s PSN} frontProcess: ProcessSerialNumber; {Front process’s PSN} inFront: Boolean; {TRUE if we’re in front} error: OSErr; procedure RecoverError (errorCode: OSErr); begin WereInFront := TRUE; EXIT(WereInFront) end; begin (* Make a PSN for this application *) thisProcess.highLongOfPSN := 0; thisProcess.lowLongOfPSN := kCurrentProcess; (* Get the PSN of the front process *) error := GetFrontProcess(frontProcess);(*<*) if error <> noErr then RecoverError(error); (* See if this application is the front process *) error := SameProcess(thisProcess, frontProcess, inFront); (*<*) if error <> noErr then RecoverError(error); WereInFront := inFront end; {$S ProcessUtils} (*******************************************************************************} {* Private: CreateDocListDesc - Create an AE descriptor for a list of documents} {*} {* This routine takes a document list in the "docList" parameter and converts it} {* to an 'appa' (typeAppParameters) AppleEvent descriptor containing a list of} {* documents to be opened by the Process Manager’s LaunchApplication routine.} {* This descriptor is returned.} {*} {* To do this, we have to first build an AppleEvent for an 'odoc'} {* (kAEOpenDocuments) or a 'pdoc' (kAEPrintDocuments) event that contains the} {* same list of documents that are contained in the "theDocList" parameter. Once} {* that’s done, this AppleEvent is coerced into an typeAppParameters descriptor.} {*} {* To create the kAEOpenDocuments or kAEPrintDocuments AppleEvent, a target} {* address descriptor is needed. Because we’re converting the AppleEvent into a} {* typeAppParameters descriptor rather than sending it somewhere, it doesn’t} {* matter what target address we use. I just used the process serial number} {* (PSN) of this application as a dummy value. Because this is an application} {* and not something like a driver, I can just use the "kCurrentProcess" constant} {* to represent this application’s PSN. A descriptor is made of this PSN, then} {* this PSN is used when creating the new kAEOpenDocuments AppleEvent. Once this} {* AppleEvent is created, the PSN descriptor is no longer needed and is disposed} {* of.} {*} {* kAEOpenDocuments AppleEvents needs a list of file aliases; each file alias} {* represents a document to open. To do this, a new descriptor list is created,} {* and an alias record is created. Then, each document in the "theDocList"} {* parameter is converted to an alias in the alias record, then the alias record} {* is manually converted into an 'alis' (typeAlias) descriptor. I did this} {* manually because an alias is already a handle, and it didn’t seem necessary to} {* go through the extra expense of the AECreateDesc routine. This typeAlias} {* descriptor is then added to the descriptor list we created earlier in this} {* paragraph. Once the entire document list is converted to the descriptor list,} {* the alias record is no longer needed and is disposed of.} {*} {* This descriptor list full of document aliases is then added to the} {* kAEOpenDocuments AppleEvent I created. Because the descriptor list is copied} {* into the AppleEvent, it is disposed of after the AEPutParamDesc call.} {*} {* Then, the most crucial call is made, AECoerceDesc. This routine coerces the} {* AppleEvent we just made into a typeAppParameters descriptor. The record} {* specified by the dataHandle of this descriptor is exactly in the same format} {* as the AppParameters record defined in the “Launching Other Applications”} {* section of the Process Manager chapter of Inside Macintosh VI.} {*} {* You can HLock the dataHandle, then set the "launchAppParameters" field of the} {* LaunchParamBlockRec to the dereferenced dataHandle. LaunchApplication can} {* then launch the specified application with the document list that was created.} {* If the launched application is high-level event aware (as specified in its} {* SIZE resource), the Process Manager converts LaunchAppParameters back to a} {* real high-level event which is processed by the application to (hopefully)} {* open the documents. Remember that LaunchAppParameters can be any high-level} {* event, but it’ll usually be used to pass an 'odoc' or 'pdoc' event, and that’s} {* what we’re using here. If the launched application isn’t high-level event} {* aware, and if LaunchAppParameters is an 'odoc' or 'pdoc' event, then the} {* Process Manager will convert the 'odoc' or 'pdoc' event into the old-style} {* application parameters so that an old-style application can still open} {* documents properly.} {*} {* This routine is important. Read it. Understand it. Then teach me how it} {* works.} {*******************************************************************************) function CreateDocListDesc (theDocList: DocListHnd; var launchDesc: AEDesc): OSErr; const kPutAtEnd = 0; {For AEPutDesc; put new descriptor at end of list} var applMessage: AppleEvent; {odoc/pdoc event w/ spec’d doc list} docDescList: AEDescList; {Desc list of doc alias descs} selfPSN: ProcessSerialNumber; {Our own PSN; not really used here} selfAddress: AEDesc; {Desc for our own PSN; ditto} docDesc: AEDesc; {Desc for doc alias} docAlias: AliasHandle; {Alias for specified docs} currDocEntry: DocListEntryHnd; {Handle to current doc list entry} openPrintCmd: AEEventID; {odoc or pdoc event?} wasChanged: Boolean; {TRUE if UpdateAlias changed alias} error: OSErr; procedure RecoverError (errorCode: Integer); begin if selfAddress.dataHandle <> nil then error := AEDisposeDesc(selfAddress);(*◊*) if applMessage.dataHandle <> nil then error := AEDisposeDesc(applMessage);(*◊*) if docDescList.dataHandle <> nil then error := AEDisposeDesc(docDescList);(*◊*) if docAlias <> nil then DisposHandle(Handle(docAlias)); CreateDocListDesc := errorCode; EXIT(CreateDocListDesc) end; begin selfAddress.dataHandle := nil; applMessage.dataHandle := nil; docDescList.dataHandle := nil; docAlias := nil; (* Make descriptor for my own PSN; just for AECreateAppleEvent’s yuks *) selfPSN.highLongOfPSN := 0; selfPSN.lowLongOfPSN := kCurrentProcess; error := AECreateDesc(typeProcessSerialNumber, @selfPSN, SIZEOF(ProcessSerialNumber), selfAddress); (*<*) if error <> noErr then RecoverError(error); (* Create an AppleEvent for our list of document descriptors *) if theDocList^^.openPrint = kOpenLaunch then openPrintCmd := kAEOpenDocuments else if theDocList^^.openPrint = kPrintLaunch then openPrintCmd := kAEPrintDocuments; error := AECreateAppleEvent(kCoreEventClass, openPrintCmd, selfAddress, kAutoGenerateReturnID, kAnyTransactionID, applMessage); (*<*) if error <> noErr then RecoverError(error); (* PSN copied into the odoc or pdoc event, so don’t need it anymore *) error := AEDisposeDesc(selfAddress);(*◊*) (* Create list of descriptors for files; don’t use list factorization *) error := AECreateList(nil, 0, FALSE, docDescList); (*<*) if error <> noErr then RecoverError(error); (* Create an alias for the first document *) currDocEntry := theDocList^^.docList; HLock(Handle(currDocEntry)); error := NewAlias(nil, currDocEntry^^.docFile, docAlias); (*<*) HUnlock(Handle(currDocEntry)); if error <> noErr then RecoverError(error); (* Put each document in the document list into document descriptor list *) while currDocEntry <> nil do begin (* Convert alias into an alias descriptor manually *) docDesc.descriptorType := typeAlias; docDesc.dataHandle := Handle(docAlias); (* Put the alias descriptor into the document descriptor list *) error := AEPutDesc(docDescList, kPutAtEnd, docDesc);(*◊*) if error <> noErr then RecoverError(error); (* Go to the next document in the document list *) currDocEntry := currDocEntry^^.next; (* Convert the next document’s FSSpec into the alias *) if currDocEntry <> nil then begin HLock(Handle(currDocEntry)); error := UpdateAlias(nil, currDocEntry^^.docFile, docAlias, wasChanged); (*<*) (*<*) HUnlock(Handle(currDocEntry)); if error <> noErr then RecoverError(error) end end; (* All aliases are in docDescList, so don’t need the alias record *) DisposHandle(Handle(docAlias)); docAlias := nil; (* Put the descriptor list of documents into the odoc or pdoc event *) error := AEPutParamDesc(applMessage, keyDirectObject, docDescList);(*◊*) if error <> noErr then RecoverError(error); (* The descriptor list is copied into the odoc event, so get rid of it *) error := AEDisposeDesc(docDescList); (* Convert the odoc event to a descriptor suitable for the launch PB *) error := AECoerceDesc(applMessage, typeAppParameters, launchDesc); (*<*) if error <> noErr then RecoverError(error); (* We’re really interested in launchDesc, so don’t need odoc message *) error := AEDisposeDesc(applMessage); CreateDocListDesc := noErr end; {$S ProcessUtils} (*******************************************************************************} {* Private: SendOpenAppEvent - Send an 'oapp' event to an application} {*} {* When an application that is high-level event aware (as indicated by the SIZE} {* resource flag), it won’t open an untitled document at launch until it gets an} {* 'oapp' AppleEvent. This routine sends an 'oapp' event to the application} {* specified by the processNum parameter.} {*} {* First, a descriptor is created that contains the process serial number of the} {* target application. This descriptor is then added to the 'oapp' AppleEvent.} {* Finally, this AppleEvent is sent to the target application which should open} {* an untitled document in response.} {*} {* This routine isn’t called if the application is to open specific documents} {* when it’s launched.} {*} {* If an error occurs, the error code is returned.} {*******************************************************************************) function SendOpenAppEvent (processNum: ProcessSerialNumber): OSErr; var theBaby: AEAddressDesc; {PSN desc. of process that’s been opened} openEvent: AppleEvent; {'oapp' AppleEvent} reply: AppleEvent; {Reply from receiving application; ignored} error: OSErr; procedure RecoverError (errorCode: Integer); begin if theBaby.dataHandle <> nil then error := AEDisposeDesc(theBaby);(*◊*) if openEvent.dataHandle <> nil then error := AEDisposeDesc(openEvent);(*◊*) if reply.dataHandle <> nil then error := AEDisposeDesc(reply);(*◊*) SendOpenAppEvent := errorCode; EXIT(SendOpenAppEvent) end; begin theBaby.dataHandle := nil; openEvent.dataHandle := nil; reply.dataHandle := nil; (* Create the Process Serial Number event descriptor *) error := AECreateDesc(typeProcessSerialNumber, Ptr(@processNum), SIZEOF(ProcessSerialNumber), theBaby); (*<*) if error <> noErr then RecoverError(error); (* Create 'oapp' event with the specified process serial number *) error := AECreateAppleEvent(kCoreEventClass, kAEOpenApplication, theBaby, kAutoGenerateReturnID, kAnyTransactionID, openEvent); (*<*) if error <> noErr then RecoverError(error); (* Send the 'oapp' event *) error := AESend(openEvent, reply, kAENoReply, kAENormalPriority, 0, nil, nil); (*<*) if error <> noErr then RecoverError(error); (* Dispose of the descriptor and event *) error := AEDisposeDesc(theBaby);(*◊*) error := AEDisposeDesc(openEvent);(*◊*) if reply.dataHandle <> nil then error := AEDisposeDesc(reply)(*◊*) end; {$S ProcessUtils} (*******************************************************************************} {* Private: LaunchProcApp - Launch an application} {*} {* This routine saves a bit of work when launching an application because it sets} {* up the launch parameter block and manages the construction of the application} {* list descriptor. The application to open is specified in processFile. If} {* there are any documents for the application to open when it’s launched, then} {* the document list is passed in docList. If there aren’t any documents for the} {* application to open, then docList is NIL. The options parameter specifies the} {* options to use when launching. This contains the values for the LaunchFlags} {* data type specified in the Process Manager chapter of Inside Macintosh VI.} {*} {* The process serial number of the specified process is returned in the} {* processNum parameter. The function result and the launchError parameter both} {* return error codes. One error is returned as a function result. These kinds} {* of errors are generated by any call that occurs during the execution of} {* LaunchProcess that is only used to manage the launching of the specified} {* application rather than launching the application itself. The launchError} {* parameter returns the error code of any error that occurs when the application} {* is actually launched.} {*******************************************************************************) function LaunchProcApp (processFile: FSSpec; docList: DocListHnd; options: LaunchFlags; var processNum: ProcessSerialNumber; var launchError: OSErr): OSErr; var launchParms: LaunchParamBlockRec; {Parameters for launching a file} launchDesc: AEDesc; {Document file list descriptor} appParms: AppParametersPtr; {Pointer to the app parameters} error: OSErr; procedure RecoverError (errorCode: OSErr); begin if launchDesc.dataHandle <> nil then begin HUnlock(Handle(launchDesc.dataHandle)); error := AEDisposeDesc(launchDesc) end; LaunchProcApp := errorCode; EXIT(LaunchProcApp) end; begin launchError := noErr; launchDesc.dataHandle := nil; (* Create the document list descriptor, if there’s a document list *) if docList <> nil then begin error := CreateDocListDesc(docList, launchDesc); (*<*) if error <> noErr then RecoverError(error); HLock(Handle(launchDesc.dataHandle)); (* Descriptor dataHandle in format suitable for LaunchApplication *) appParms := AppParametersPtr(launchDesc.dataHandle^) end else appParms := nil; (* Set up the launch parameters *) with launchParms do begin (*WITH*) launchBlockID := extendedBlock; (*WITH*) launchEPBLength := extendedBlockLen; (*WITH*) launchFileFlags := 0; (*WITH*) launchControlFlags := options; (*WITH*) launchAppSpec := @processFile; (*WITH*) launchAppParameters := appParms end; (* Launch the process *) launchError := LaunchApplication(@launchParms); (* Get rid of the document descriptor list, if there was one *) if docList <> nil then begin HUnlock(Handle(launchDesc.dataHandle)); error := AEDisposeDesc(launchDesc) end else (* No document descriptor list, so send oapp AppleEvent *) if launchError = noErr then error := SendOpenAppEvent(launchParms.launchProcessSN); (* If the launch was successful, return the PSN of the process *) if launchError = noErr then processNum := launchParms.launchProcessSN; LaunchProcApp := noErr end; {$S ProcessUtils} (*******************************************************************************} {* Private: GetFirstDAName - Get name of first } {*} {* GetFirstDAName gets the name of the first desk accessory in the file specified} {* by daFile. This name is returned in the daName parameter. GetFirstDAName} {* returns TRUE if a desk accessory was found in the file. If no desk accessory} {* could be found, then GetFirstDAName returns FALSE and the daName parameter is} {* unchanged.} {*} {* Desk accessories are stored in DRVR resources, but device drivers are also} {* stored in DRVR resources. GetFirstDAName will not return the names of device} {* drivers. How do you tell whether a DRVR resource is a desk accessory or a} {* device driver? Simply check on the first character of the DRVR resource’s} {* name. If it’s a null character, then you’ve got a desk accessory. If it’s} {* any other character, then you’ve got a device driver.} {*******************************************************************************) function GetFirstDAName (daFile: FSSpec; var daName: Str255): Boolean; var currRF: Integer; {Ref num of current resource file} daRF: Integer; {Ref num of DA’s resource file} drvrRsrc: Handle; {Handle to a DRVR resource} drvrID: Integer; {ID number of drvrRsrc} drvrType: ResType; {Type of resource} drvrName: Str255; {Name of resource} numDRVRs: Integer; {Number of DRVR resources in the file} index: Integer; {Index into DRVR resources} daFound: Boolean; {TRUE if a DA was found in daFile} begin daFound := FALSE; (* Save the current resource file ref num so we can restore it later *) currRF := CurResFile; (* Open the specified resource file *) daRF := FSpOpenResFile(daFile, fsRdPerm); if daRF <> -1 then begin (* Ready to check each DRVR; only checking, so set ResLoad false *) SetResLoad(FALSE); index := 1; numDRVRs := Count1Resources('DRVR'); (* Check each DRVR until all checked or a DA was found *) while (index <= numDRVRs) and (not daFound) do begin drvrRsrc := Get1IndResource('DRVR', index); (* Get information about the resource *) if drvrRsrc <> nil then begin GetResInfo(drvrRsrc, drvrID, drvrType, drvrName); (*<*) (*<*) (*<*) (* Find out whether it’s a DA or not *) if (drvrName[0] > CHR(0)) & (drvrName[1] = CHR(0)) then daFound := TRUE else index := SUCC(index) end end; (* Restore the resource file and reset ResLoad *) SetResLoad(TRUE); CloseResFile(daRF) end; (* Get name of first DA in the file, or nil string if none found *) if daFound then daName := drvrName else daName[0] := CHR(0); GetFirstDAName := daFound; UseResFile(currRF) end; {$S ProcessUtils} (*******************************************************************************} {* Public: FindProcess} {*} {* The critical routine called by FindProcess is the GetProcessInfo routine. The} {* process information it returns is checked against "testFile", and against} {* "daName" if a desk accessory is being checked. If they’re equal, then} {* FindProcess returns TRUE and puts the process information in "testFileInfo".} {* If the entire Process Manager process list is searched without finding a} {* match, then FALSE is returned.} {*******************************************************************************) function FindProcess (testFile: FSSpec; daName: StringPtr; var testFileInfo: ProcessInfoRec): Boolean; var procInfo: ProcessInfoRec; {Information on process being checked} procNum: ProcessSerialNumber; {Serial num of process being checked} procName: Str31; {Name of open process} procSpec: FSSpec; {File spec of open process’s file} procFound: Boolean; {TRUE if matching DA was found} error: OSErr; begin (* Start checking from first process in Process Manager’s list *) procNum.highLongOfPSN := 0; procNum.lowLongOfPSN := kNoProcess; (* Loop through entire list of open processes or until match *) procFound := FALSE; while (not procFound) & (GetNextProcess(procNum) = noErr) do(*◊*) begin (* Get information about an open process *) procInfo.processInfoLength := SIZEOF(ProcessInfoRec); procInfo.processName := @procName; procInfo.processAppSpec := @procSpec; error := GetProcessInformation(procNum, procInfo); (*◊*) (* Is it the same file as the one we’re testing? *) if error <> noErr then if EqualFSSpec(procInfo.processAppSpec^, testFile) then (* Yes; if it’s an application, we’ve found matching app *) if BAND(procInfo.processMode, modeDeskAccessory) = 0 then procFound := TRUE else (* Is it the same DA as the one we’re testing?} { IF EqualString (procInfo.processName^, daName^,} { NOT kCaseSens, kDiacSens) THEN} { (* Yes, we’ve found the matching DA *) procFound := TRUE end; if procFound then testFileInfo := procInfo; FindProcess := procFound end; {$S ProcessUtils} (*******************************************************************************} {* Private: LaunchProcDA - Launch a desk accessory} {*} {* This routine launches the desk accessory that’s in the file specified by} {* process file and with the name specified by daName. If daName is NIL, then} {* the first desk accessory in the file, according to Get1IndResource, is} {* launched.} {*} {* As of 7.0b1, LaunchDeskAccessory doesn’t quite work as advertised in Inside} {* Macintosh VI if the desk accessory that it’s launching is already open.} {* Inside Macintosh VI says that the open desk accessory that’s being launched is} {* simply brought to the front. What I’ve found is that LaunchDeskAccessory} {* doesn’t do anything but return opWrErr. If the file that the desk accessory} {* is in is locked, then a new copy of the desk accessory is opened. I didn’t} {* quite like either of those actions, so I explicitly check to see whether the} {* desk accessory that I’m about to launch is already open. If it is, then I} {* call SetFrontProcess myself to bring it to the front.} {*} {* To check for this case, I first make sure that I have a name for the desk} {* accessory. LaunchDeskAccessory accepts NIL as a desk accessory name if the} {* caller wants to launch the first desk accessory that the Process Manager finds} {* in the specified file. My routine, LaunchProcess, also allows this. But I} {* don’t know what the Process Manager thinks is the first desk accessory in the} {* file. Since I’m comparing the characteristics of the desk accessory I’m about} {* to open against the list of open processes, and since I don’t know which desk} {* accessory is about to be launched if I leave the choice up to the Process} {* Manager, I just go and get the first desk accessory in the specified file} {* myself by calling my routine, GetFirstDAName. Now that I’m guaranteed to have} {* a name for the desired desk accessory, I don’t have to second-guess the} {* Process Manager’s choice for the “first” desk accessory in the specified file.} {*} {* You can’t tell LaunchDeskAccessory that you want to terminate after the desk} {* accessory is launched, so I terminate myself by calling ExitToShell if the} {* terminate parameter was set to TRUE. You also can’t tell LaunchDeskAccessory} {* that you want the desk accessory launched into the background. I still} {* haven’t thought of a clean way to do this, so for now, I don’t offer that as} {* an option. I can think of a few dirty ways.} {*******************************************************************************) function LaunchProcDA (processFile: FSSpec; daName: StringPtr; options: LaunchFlags; var processNum: ProcessSerialNumber; var launchError: OSErr): OSErr; var procInfo: ProcessInfoRec; {Used to search open processes} procNum: ProcessSerialNumber; {Process serial num of open process} firstDAName: Str255; {File’s 1st DA name if none spec’d} daOpen: Boolean; {TRUE if spec’d DA is already open} error: OSErr; begin error := noErr; launchError := noErr; (* If no DA name specified, get name of first DA in processFile *) if daName = nil then if GetFirstDAName(processFile, firstDAName) then (*<*) if firstDAName[0] > CHR(0) then daName := @firstDAName else daName := nil; (* If got name for new DA, see if DA process open w/ same name and file *) if daName <> nil then daOpen := FindProcess(processFile, daName, procInfo) (*<*) else daOpen := FALSE; (* Launch the DA to the front, or set it to front if already launched *) if daOpen then error := SetFrontProcess(procInfo.processNumber) else launchError := LaunchDeskAccessory(@processFile, daName); (* If the terminate flag is set, glad to oblige *) if BAND(options, launchContinue) = 0 then ExitToShell; (* Return the process serial number of the DA *) if (error = noErr) and (launchError = noErr) then begin daOpen := FindProcess(processFile, daName, procInfo); (*<*) if daOpen then processNum := procInfo.processNumber else begin procInfo.processNumber.highLongOfPSN := 0; procInfo.processNumber.lowLongOfPSN := kNoProcess; processNum := procInfo.processNumber end end; LaunchProcDA := error end; {$S ProcessUtils} (*******************************************************************************} {* Public: LaunchProcess} {*} {* The terminate and foreground parmeters are used to set up the flags for} {* launching in the launchOptions variable. The launch parameter block is set up} {* with the specified file and launch flags.} {*} {* If the launchContinue flags is clear, then the LaunchApplication function} {* doesn’t return even if it fails.} {*******************************************************************************) function LaunchProcess (processFile: FSSpec; daName: StringPtr; docList: DocListHnd; options: LaunchFlags; var returnPSN: ProcessSerialNumber; var launchError: OSErr): OSErr; var fileInfo: FInfo; {File information for file to launch} newPSN: ProcessSerialNumber; {PSN of launched application} error: OSErr; procedure RecoverError (errorCode: OSErr); begin LaunchProcess := errorCode; EXIT(LaunchProcess) end; begin launchError := noErr; (* Find out whether file to launch is APPL or something else *) error := FSpGetFInfo(processFile, fileInfo); (*<*) if error <> noErr then RecoverError(error); (* Launch the process *) if fileInfo.fdType = 'APPL' then begin (* File to be launched is an application; launch it *) error := LaunchProcApp(processFile, docList, options, newPSN, launchError); (*<*) (*<*) if error <> noErr then RecoverError(error); end else begin (* File to be launch is a desk accessory; launch it *) error := LaunchProcDA(processFile, daName, options, newPSN, launchError); (*<*) (*<*) if error <> noErr then RecoverError(error) end; (* Return the PSN of the new process *) if launchError = noErr then returnPSN := newPSN; LaunchProcess := noErr end; {$S ProcessUtils} (*******************************************************************************} {* Public: CountProcesses} {*} {* It’s a pretty simple and straightforward algorithm.} {*******************************************************************************) function CountProcesses: Integer; var procNum: ProcessSerialNumber; {Serial num of process being checked} procCount: Integer; {Number of processes found} begin (* Start checking from first process in Process Manager’s list *) procNum.highLongOfPSN := 0; procNum.lowLongOfPSN := kNoProcess; (* Loop through entire list of open processes *) procCount := 0; while GetNextProcess(procNum) = noErr do(*◊*) procCount := SUCC(procCount); (* Return the number of processes found *) CountProcesses := procCount end; {$S ProcessUtils} (*******************************************************************************} {* Public: TerminateProcess} {*} {* Terminating a process is done by sending a 'quit' AppleEvent to the process} {* whose process serial number is given by "theProcessNum".} {*******************************************************************************) function TerminateProcess (theProcessNum: ProcessSerialNumber): OSErr; var theDoomed: AEAddressDesc; {PSN descriptor of process to be terminated} quitEvent: AppleEvent; {'quit' AppleEvent} reply: AppleEvent; {Reply from receiving application; ignored} error: OSErr; procedure RecoverError (error: Integer); var result: OSErr; begin if theDoomed.dataHandle <> nil then result := AEDisposeDesc(theDoomed);(*◊*) if quitEvent.dataHandle <> nil then result := AEDisposeDesc(quitEvent);(*◊*) TerminateProcess := error; EXIT(TerminateProcess) end; begin theDoomed.dataHandle := nil; quitEvent.dataHandle := nil; reply.dataHandle := nil; (* Create the Process Serial Number event descriptor *) error := AECreateDesc(typeProcessSerialNumber, Ptr(@theProcessNum), SIZEOF(theProcessNum), theDoomed); (*<*) if error <> noErr then RecoverError(error); (* Create 'quit' event with the specified process serial number *) error := AECreateAppleEvent(kCoreEventClass, kAEQuitApplication, theDoomed, kAutoGenerateReturnID, kAnyTransactionID, quitEvent); (*<*) if error <> noErr then RecoverError(error); (* Send the 'quit' event *) error := AESend(quitEvent, reply, kAENoReply, kAENormalPriority, kNoTimeOut, nil, nil); (*<*) if error <> noErr then RecoverError(error); (* PSN in the AppleEvent, so can dispose of PSN descriptor *) error := AEDisposeDesc(theDoomed);(*◊*) (* Dispose of the 'quit' AppleEvent *) error := AEDisposeDesc(quitEvent)(*◊*) end; end.